You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

229 lines
6.9 KiB

import { beforeEach, describe, expect, mock, test } from "bun:test";
const ownerRecord = {
id: 310001,
publicSlug: "consistency-user",
nickname: "Consistency",
avatar: null,
avatarVisibility: "public",
bioVisibility: "public",
bioMarkdown: "",
socialLinksJson: "[]",
status: "active",
};
const selectMock = mock(() => ({
from: () => ({
where: () => ({
limit: async () => [ownerRecord],
}),
}),
}));
const parseSocialLinksJson = mock(() => []);
const normalizePublicListPage = mock((pageRaw: unknown) => {
const n = Number(pageRaw);
return Number.isFinite(n) && n >= 1 ? Math.trunc(n) : 1;
});
function publicTotal<T extends { visibility: string }>(rows: T[]) {
return rows.filter((row) => row.visibility === "public").length;
}
const previewPosts = [
{ slug: "preview-post-a", visibility: "public" },
{ slug: "preview-post-b", visibility: "public" },
{ slug: "preview-post-hidden", visibility: "private" },
];
const previewTimeline = [
{ id: 301, visibility: "public" },
{ id: 302, visibility: "public" },
{ id: 303, visibility: "private" },
];
const previewReading = [
{ id: 401, visibility: "public" },
{ id: 402, visibility: "public" },
{ id: 403, visibility: "unlisted" },
];
const pagePosts = [
{ slug: "page-post-1", visibility: "public" },
{ slug: "page-post-2", visibility: "private" },
{ slug: "page-post-3", visibility: "public" },
];
const pageTimeline = [
{ id: 501, visibility: "private" },
{ id: 502, visibility: "public" },
{ id: 503, visibility: "public" },
];
const pageReading = [
{ id: 601, visibility: "public" },
{ id: 602, visibility: "unlisted" },
{ id: 603, visibility: "public" },
];
const getPublicPostsPreviewBySlug = mock(async () => ({
items: previewPosts.filter((row) => row.visibility === "public"),
total: publicTotal(previewPosts),
}));
const getPublicTimelinePreviewBySlug = mock(async () => ({
items: previewTimeline.filter((row) => row.visibility === "public"),
total: publicTotal(previewTimeline),
}));
const getPublicRssPreviewBySlug = mock(async () => ({
items: previewReading.filter((row) => row.visibility === "public"),
total: publicTotal(previewReading),
}));
const getPublicPostsPageBySlug = mock(async () => ({
items: pagePosts.filter((row) => row.visibility === "public"),
total: publicTotal(pagePosts),
page: 1,
pageSize: 10,
}));
const getPublicTimelinePageBySlug = mock(async () => ({
items: pageTimeline.filter((row) => row.visibility === "public"),
total: publicTotal(pageTimeline),
page: 1,
pageSize: 10,
}));
const getPublicRssPageBySlug = mock(async () => ({
items: pageReading.filter((row) => row.visibility === "public"),
total: publicTotal(pageReading),
page: 1,
pageSize: 10,
}));
mock.module("drizzle-pkg/lib/db", () => ({
dbGlobal: {
select: selectMock,
},
}));
mock.module("drizzle-pkg/lib/schema/auth", () => ({
users: {
id: "id",
publicSlug: "publicSlug",
status: "status",
},
}));
mock.module("drizzle-orm", () => ({
and: (...conditions: unknown[]) => conditions,
eq: (a: unknown, b: unknown) => [a, b],
}));
mock.module("#server/service/profile", () => ({
parseSocialLinksJson,
}));
let realGetPublicHubBySlug: ((...args: unknown[]) => unknown) | undefined;
mock.module("#server/service/public-hub", () => ({
getPublicHubBySlug: (...args: unknown[]) => {
if (!realGetPublicHubBySlug) {
throw new Error("realGetPublicHubBySlug not initialized");
}
return realGetPublicHubBySlug(...args);
},
}));
mock.module("#server/service/posts", () => ({
getPublicPostsPreviewBySlug,
getPublicPostsPageBySlug,
}));
mock.module("#server/service/timeline", () => ({
getPublicTimelinePreviewBySlug,
getPublicTimelinePageBySlug,
}));
mock.module("#server/service/rss", () => ({
getPublicRssPreviewBySlug,
getPublicRssPageBySlug,
}));
({ getPublicHubBySlug: realGetPublicHubBySlug } = await import("../../../../service/public-hub"));
mock.module("#server/utils/public-pagination", () => ({
normalizePublicListPage,
}));
mock.module("h3", () => ({
defineEventHandler: (handler: unknown) => handler,
createError: (input: { statusCode: number; statusMessage: string }) => {
const error = new Error(input.statusMessage) as Error & {
statusCode: number;
statusMessage: string;
};
error.statusCode = input.statusCode;
error.statusMessage = input.statusMessage;
return error;
},
getQuery: (event: { query?: Record<string, unknown> }) => event.query ?? {},
}));
globalThis.defineEventHandler ??= ((handler: unknown) => handler) as never;
globalThis.createError ??= ((input: { statusCode: number; statusMessage: string }) => {
const error = new Error(input.statusMessage) as Error & {
statusCode: number;
statusMessage: string;
};
error.statusCode = input.statusCode;
error.statusMessage = input.statusMessage;
return error;
}) as never;
globalThis.getQuery ??= ((event: { query?: Record<string, unknown> }) => event.query ?? {}) as never;
globalThis.R ??= {
success: (data: unknown) => data,
};
const profileHandler = (await import("../[publicSlug].get")).default;
const postsHandler = (await import("./posts/index.get")).default;
const timelineHandler = (await import("./timeline/index.get")).default;
const readingHandler = (await import("./reading/index.get")).default;
describe("public profile totals consistency", () => {
beforeEach(() => {
selectMock.mockClear();
parseSocialLinksJson.mockClear();
getPublicPostsPreviewBySlug.mockClear();
getPublicTimelinePreviewBySlug.mockClear();
getPublicRssPreviewBySlug.mockClear();
getPublicPostsPageBySlug.mockClear();
getPublicTimelinePageBySlug.mockClear();
getPublicRssPageBySlug.mockClear();
normalizePublicListPage.mockClear();
});
test("同一 slug 下 hub.modules.<x>.total 与对应子页 total 一致(public-only)", async () => {
const [profileRes, postsRes, timelineRes, readingRes] = await Promise.all([
profileHandler({ context: { params: { publicSlug: ownerRecord.publicSlug } } } as never),
postsHandler({
context: { params: { publicSlug: ownerRecord.publicSlug } },
query: { page: "1" },
} as never),
timelineHandler({
context: { params: { publicSlug: ownerRecord.publicSlug } },
query: { page: "1" },
} as never),
readingHandler({
context: { params: { publicSlug: ownerRecord.publicSlug } },
query: { page: "1" },
} as never),
]);
expect(profileRes.modules.posts.total).toBe(postsRes.total);
expect(profileRes.modules.timeline.total).toBe(timelineRes.total);
expect(profileRes.modules.reading.total).toBe(readingRes.total);
expect(profileRes.modules.posts.total).toBe(profileRes.posts.total);
expect(profileRes.modules.timeline.total).toBe(profileRes.timeline.total);
expect(profileRes.modules.reading.total).toBe(profileRes.rssItems.total);
expect(postsRes.total).toBe(2);
expect(timelineRes.total).toBe(2);
expect(readingRes.total).toBe(2);
});
});